/*
 * JoinComponentDefinition.java
 *
 * Created on September 27, 2006, 11:37 PM
 *
 * (C) 2006 R. Alexander Milowski
 */

package com.smallx.xproc.library;

import com.smallx.xpath.XPathMatcher;
import com.smallx.xpath.XPathMatcherFactory;
import com.smallx.xproc.ComponentType;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.logging.Level;
import org.infoset.component.Component;
import org.infoset.component.ComponentBase;
import org.infoset.component.ComponentDefinition;
import org.infoset.xml.Document;
import org.infoset.xml.Item;
import org.infoset.xml.ItemConstructor;
import org.infoset.xml.ItemDestination;
import org.infoset.xml.Name;
import org.infoset.xml.Named;
import org.infoset.xml.Parent;
import org.infoset.xml.XMLException;
import org.infoset.xml.util.DocumentSource;

/**
 *
 * @author alex
 */
public class ViewportStartComponentDefinition implements ComponentDefinition {

   static ComponentType defaultType = new ComponentType(LibraryComponents.VIEWPORT_START_TYPE);
   public static Name INPUT_PORT = Name.create("input");
   public static Name MATCHES_PORT = Name.create("matches");
   public static Name NON_MATCHES_PORT = Name.create("non-matches");
   static {
      defaultType.addInputPort(INPUT_PORT,false);
      defaultType.addOutputPort(MATCHES_PORT,false);
      defaultType.addOutputPort(NON_MATCHES_PORT,false);
   }
   
   class Start extends ComponentBase implements IterationNotifier {
      
      ItemDestination matches;
      ItemDestination nonMatches;
      URI inputLocation;
      Document inputDocument;
      List<IterationListener> iterListeners;
      
      Start() {
         super(type.getName());
         iterListeners = new ArrayList<IterationListener>(1);
      }
      
      public void init(Component.Context context) 
         throws XMLException
      {
         super.init(context);
         if (expression==null) {
            throw new IllegalStateException("The viewport expression has not been set for "+type.getName());
         }
      }
      
      public void addIterationListener(IterationListener listener) {
         iterListeners.add(listener);
      }
   
      public void bindResource(Name port, URI location) throws XMLException {
         if (type.getInputPort(port)==null) {
            super.bindResource(port,location);
            return;
         }
         inputLocation = location;
      }

      public void bindResource(Name port, Document doc) throws XMLException {
         if (type.getInputPort(port)==null) {
            super.bindResource(port,doc);
            return;
         }
         inputDocument = doc;
      }
   
   
      public void bindOutputPort(Name port, ItemDestination output) throws XMLException {
         if (port.equals(NON_MATCHES_PORT)) {
            nonMatches = output;
         } else if (type.getOutputPort(port)!=null) {
            matches = output;
         } else {
            super.bindOutputPort(port,output);
         }
      }
   
      public void onStart()
         throws XMLException
      {
         if (inputDocument!=null) {
            DocumentSource.generate(inputDocument,false,getDestination());
         } else if (inputLocation!=null) {
            try {
               context.getDocumentLoader().generate(inputLocation,getDestination());
            } catch (IOException ex) {
               throw new XMLException("Cannot load static document reference "+inputLocation+" for port "+type.getInputPortNames().iterator().next()+" on step "+type.getName());
            }
         }
      }
   
      public ItemDestination getInputPort(Name port) 
         throws XMLException
      {
         if (type.getInputPort(port)==null) {      
            return super.getInputPort(port);
         }
         return getDestination();
      }

      public void onDocumentInput(Name port, Document doc) 
         throws XMLException 
      {
         if (type.getInputPort(port)==null) {      
            super.getInputPort(port);
            return;
         }
         DocumentSource.generate(doc,false,getDestination());
      }
   
      protected ItemDestination getDestination() {
         return new ItemDestination() {
             XPathMatcher matcher = expression.newMatcher();
             int sentLevel = -1;
             ItemConstructor currentConstructor = null;
             ItemDestination inner;
             boolean isFineLog = context.getLog().isLoggable(Level.FINE);
             public void send(Item item) 
                throws XMLException
             {
               if (matcher.match(item)!=null) {
                  if (isFineLog) {
                     String name = "";
                     if (item instanceof Named) {
                        name = ((Named)item).getName().toString();
                     }
                     context.getLog().fine("Matched "+item.getType()+" "+name);
                  }
                  if (sentLevel<0 && item.getType()!=Item.ItemType.DocumentItem) {
                     if (isFineLog) {
                        context.getLog().fine("Start on "+item);
                     }
                     for (IterationListener listener : iterListeners) {
                        if (isFineLog) {
                           context.getLog().fine("Notifying start on "+listener);
                        }
                        listener.notifyStart();
                     }
                     currentConstructor = context.getInfoset().createItemConstructor();
                     URI base = item instanceof Parent ? 
                        ((Parent)item).getBaseURI() : 
                        null;
                     processMatch(currentConstructor.createDocument(base));
                  }
                  switch (item.getType()) {
                     case DocumentItem:
                        sentLevel++;
                        break;
                     case ElementItem:
                        sentLevel++;
                        if (sentLevel==0) {
                           // TODO: this shouldn't be need in streaming.  All elements should have localized declarations.
                           //((Element)item).localizeNamespaceDeclarations();
                        }
                        break;
                     case ElementEndItem:
                     case DocumentEndItem:
                        sentLevel--;
                  }
                  processMatch(item);
                  if (sentLevel<0 && item.getType()!=Item.ItemType.DocumentEndItem) {
                     processMatch(currentConstructor.createDocumentEnd());
                     for (IterationListener listener : iterListeners) {
                        if (isFineLog) {
                           context.getLog().fine("Notifying end on "+listener);
                        }
                        listener.notifyEnd();
                     }
                  }
               } else {
                  post(item);
               }

             }

             /*
              * Send the non-matches to the output
              */
             void post(Item item)
                throws XMLException
             {
                nonMatches.send(item);
             }

             /*
              * Send the matches to the inner steps
              */
             void processMatch(Item item)
                throws XMLException
             {
                if (matches!=null) {
                   matches.send(item.copyOfItem(false));
                }
             }

         };
      }
   }
   protected ComponentType type;
   protected XPathMatcherFactory expression;
   
   public ViewportStartComponentDefinition() {
      this(defaultType);
   }
   /** Creates a new instance of FlowComponentDefinition */
   public ViewportStartComponentDefinition(ComponentType type) {
      this.type = type;
   }
   
   public void setExpression(XPathMatcherFactory exp) {
      this.expression = exp;
   }
   
   public Component newInstance()
      throws XMLException 
   {
      return new Start();
   }
      
   public Name getName() {
      return type.getName();
   }

   public String getVersion() {
      return null;
   }

   public String getVendor() {
      return null;
   }

   public URI getVendorLocation() {
      return null;
   }

   public Collection<Name> getInputs() {
      return type.getInputPortNames();
   }

   public Collection<Name> getOutputs() {
      return type.getOutputPortNames();
   }

}

